<!doctype html>
<html>
	<head>
		<meta charset="utf-8">
		<title> 壁虎</title>
		<script src="http://www.jq22.com/jquery/jquery-1.10.2.js"></script>
		<style>
		</style>
	</head>
	<body>
		<div></div>

		<script>
			var Input = {
				keys: [],
				mouse: {
					left: false,
					right: false,
					middle: false,
					x: 0,
					y: 0
				}
			};
			for (var i = 0; i < 230; i++) {
				Input.keys.push(false);
			}
			document.addEventListener("keydown", function(event) {
				Input.keys[event.keyCode] = true;
			});
			document.addEventListener("keyup", function(event) {
				Input.keys[event.keyCode] = false;
			});
			document.addEventListener("mousedown", function(event) {
				if ((event.button = 0)) {
					Input.mouse.left = true;
				}
				if ((event.button = 1)) {
					Input.mouse.middle = true;
				}
				if ((event.button = 2)) {
					Input.mouse.right = true;
				}
			});
			document.addEventListener("mouseup", function(event) {
				if ((event.button = 0)) {
					Input.mouse.left = false;
				}
				if ((event.button = 1)) {
					Input.mouse.middle = false;
				}
				if ((event.button = 2)) {
					Input.mouse.right = false;
				}
			});
			document.addEventListener("mousemove", function(event) {
				Input.mouse.x = event.clientX;
				Input.mouse.y = event.clientY;
			});
			//Sets up canvas
			var canvas = document.createElement("canvas");
			document.body.appendChild(canvas);
			canvas.width = Math.max(window.innerWidth, window.innerWidth);
			canvas.height = Math.max(window.innerWidth, window.innerWidth);
			canvas.style.position = "absolute";
			canvas.style.left = "0px";
			canvas.style.top = "0px";
			document.body.style.overflow = "hidden";
			var ctx = canvas.getContext("2d");
			//Necessary classes
			var segmentCount = 0;
			class Segment {
				constructor(parent, size, angle, range, stiffness) {
					segmentCount++;
					this.isSegment = true;
					this.parent = parent; //Segment which this one is connected to
					if (typeof parent.children == "object") {
						parent.children.push(this);
					}
					this.children = []; //Segments connected to this segment
					this.size = size; //Distance from parent
					this.relAngle = angle; //Angle relative to parent
					this.defAngle = angle; //Default angle relative to parent
					this.absAngle = parent.absAngle + angle; //Angle relative to x-axis
					this.range = range; //Difference between maximum and minimum angles
					this.stiffness = stiffness; //How closely it conforms to default angle
					this.updateRelative(false, true);
				}
				updateRelative(iter, flex) {
					this.relAngle =
						this.relAngle -
						2 *
						Math.PI *
						Math.floor((this.relAngle - this.defAngle) / 2 / Math.PI + 1 / 2);
					if (flex) {
						//		this.relAngle=this.range/
						//				(1+Math.exp(-4*(this.relAngle-this.defAngle)/
						//				(this.stiffness*this.range)))
						//			  -this.range/2+this.defAngle;
						this.relAngle = Math.min(
							this.defAngle + this.range / 2,
							Math.max(
								this.defAngle - this.range / 2,
								(this.relAngle - this.defAngle) / this.stiffness + this.defAngle
							)
						);
					}
					this.absAngle = this.parent.absAngle + this.relAngle;
					this.x = this.parent.x + Math.cos(this.absAngle) * this.size; //Position
					this.y = this.parent.y + Math.sin(this.absAngle) * this.size; //Position
					if (iter) {
						for (var i = 0; i < this.children.length; i++) {
							this.children[i].updateRelative(iter, flex);
						}
					}
				}
				draw(iter) {
					ctx.beginPath();
					ctx.moveTo(this.parent.x, this.parent.y);
					ctx.lineTo(this.x, this.y);
					ctx.stroke();
					if (iter) {
						for (var i = 0; i < this.children.length; i++) {
							this.children[i].draw(true);
						}
					}
				}
				follow(iter) {
					var x = this.parent.x;
					var y = this.parent.y;
					var dist = ((this.x - x) ** 2 + (this.y - y) ** 2) ** 0.5;
					this.x = x + this.size * (this.x - x) / dist;
					this.y = y + this.size * (this.y - y) / dist;
					this.absAngle = Math.atan2(this.y - y, this.x - x);
					this.relAngle = this.absAngle - this.parent.absAngle;
					this.updateRelative(false, true);
					//this.draw();
					if (iter) {
						for (var i = 0; i < this.children.length; i++) {
							this.children[i].follow(true);
						}
					}
				}
			}
			class LimbSystem {
				constructor(end, length, speed, creature) {
					this.end = end;
					this.length = Math.max(1, length);
					this.creature = creature;
					this.speed = speed;
					creature.systems.push(this);
					this.nodes = [];
					var node = end;
					for (var i = 0; i < length; i++) {
						this.nodes.unshift(node);
						//node.stiffness=1;
						node = node.parent;
						if (!node.isSegment) {
							this.length = i + 1;
							break;
						}
					}
					this.hip = this.nodes[0].parent;
				}
				moveTo(x, y) {
					this.nodes[0].updateRelative(true, true);
					var dist = ((x - this.end.x) ** 2 + (y - this.end.y) ** 2) ** 0.5;
					var len = Math.max(0, dist - this.speed);
					for (var i = this.nodes.length - 1; i >= 0; i--) {
						var node = this.nodes[i];
						var ang = Math.atan2(node.y - y, node.x - x);
						node.x = x + len * Math.cos(ang);
						node.y = y + len * Math.sin(ang);
						x = node.x;
						y = node.y;
						len = node.size;
					}
					for (var i = 0; i < this.nodes.length; i++) {
						var node = this.nodes[i];
						node.absAngle = Math.atan2(
							node.y - node.parent.y,
							node.x - node.parent.x
						);
						node.relAngle = node.absAngle - node.parent.absAngle;
						for (var ii = 0; ii < node.children.length; ii++) {
							var childNode = node.children[ii];
							if (!this.nodes.includes(childNode)) {
								childNode.updateRelative(true, false);
							}
						}
					}
					//this.nodes[0].updateRelative(true,false)
				}
				update() {
					this.moveTo(Input.mouse.x, Input.mouse.y);
				}
			}
			class LegSystem extends LimbSystem {
				constructor(end, length, speed, creature) {
					super(end, length, speed, creature);
					this.goalX = end.x;
					this.goalY = end.y;
					this.step = 0; //0 stand still, 1 move forward,2 move towards foothold
					this.forwardness = 0;

					//For foot goal placement
					this.reach =
						0.9 *
						((this.end.x - this.hip.x) ** 2 + (this.end.y - this.hip.y) ** 2) ** 0.5;
					var relAngle =
						this.creature.absAngle -
						Math.atan2(this.end.y - this.hip.y, this.end.x - this.hip.x);
					relAngle -= 2 * Math.PI * Math.floor(relAngle / 2 / Math.PI + 1 / 2);
					this.swing = -relAngle + (2 * (relAngle < 0) - 1) * Math.PI / 2;
					this.swingOffset = this.creature.absAngle - this.hip.absAngle;
					//this.swing*=(2*(relAngle>0)-1);
				}
				update(x, y) {
					this.moveTo(this.goalX, this.goalY);
					//this.nodes[0].follow(true,true)
					if (this.step == 0) {
						var dist =
							((this.end.x - this.goalX) ** 2 + (this.end.y - this.goalY) ** 2) **
							0.5;
						if (dist > 1) {
							this.step = 1;
							//this.goalX=x;
							//this.goalY=y;
							this.goalX =
								this.hip.x +
								this.reach *
								Math.cos(this.swing + this.hip.absAngle + this.swingOffset) +
								(2 * Math.random() - 1) * this.reach / 2;
							this.goalY =
								this.hip.y +
								this.reach *
								Math.sin(this.swing + this.hip.absAngle + this.swingOffset) +
								(2 * Math.random() - 1) * this.reach / 2;
						}
					} else if (this.step == 1) {
						var theta =
							Math.atan2(this.end.y - this.hip.y, this.end.x - this.hip.x) -
							this.hip.absAngle;
						var dist =
							((this.end.x - this.hip.x) ** 2 + (this.end.y - this.hip.y) ** 2) **
							0.5;
						var forwardness2 = dist * Math.cos(theta);
						var dF = this.forwardness - forwardness2;
						this.forwardness = forwardness2;
						if (dF * dF < 1) {
							this.step = 0;
							this.goalX = this.hip.x + (this.end.x - this.hip.x);
							this.goalY = this.hip.y + (this.end.y - this.hip.y);
						}
					}
					//	ctx.strokeStyle='blue';
					//	ctx.beginPath();
					//	ctx.moveTo(this.end.x,this.end.y);
					//	ctx.lineTo(this.hip.x+this.reach*Math.cos(this.swing+this.hip.absAngle+this.swingOffset),
					//				this.hip.y+this.reach*Math.sin(this.swing+this.hip.absAngle+this.swingOffset));
					//	ctx.stroke();
					//	ctx.strokeStyle='black';
				}
			}
			class Creature {
				constructor(
					x,
					y,
					angle,
					fAccel,
					fFric,
					fRes,
					fThresh,
					rAccel,
					rFric,
					rRes,
					rThresh
				) {
					this.x = x; //Starting position
					this.y = y;
					this.absAngle = angle; //Staring angle
					this.fSpeed = 0; //Forward speed
					this.fAccel = fAccel; //Force when moving forward
					this.fFric = fFric; //Friction against forward motion
					this.fRes = fRes; //Resistance to motion
					this.fThresh = fThresh; //minimum distance to target to keep moving forward
					this.rSpeed = 0; //Rotational speed
					this.rAccel = rAccel; //Force when rotating
					this.rFric = rFric; //Friction against rotation
					this.rRes = rRes; //Resistance to rotation
					this.rThresh = rThresh; //Maximum angle difference before rotation
					this.children = [];
					this.systems = [];
				}
				follow(x, y) {
					var dist = ((this.x - x) ** 2 + (this.y - y) ** 2) ** 0.5;
					var angle = Math.atan2(y - this.y, x - this.x);
					//Update forward
					var accel = this.fAccel;
					if (this.systems.length > 0) {
						var sum = 0;
						for (var i = 0; i < this.systems.length; i++) {
							sum += this.systems[i].step == 0;
						}
						accel *= sum / this.systems.length;
					}
					this.fSpeed += accel * (dist > this.fThresh);
					this.fSpeed *= 1 - this.fRes;
					this.speed = Math.max(0, this.fSpeed - this.fFric);
					//Update rotation
					var dif = this.absAngle - angle;
					dif -= 2 * Math.PI * Math.floor(dif / (2 * Math.PI) + 1 / 2);
					if (Math.abs(dif) > this.rThresh && dist > this.fThresh) {
						this.rSpeed -= this.rAccel * (2 * (dif > 0) - 1);
					}
					this.rSpeed *= 1 - this.rRes;
					if (Math.abs(this.rSpeed) > this.rFric) {
						this.rSpeed -= this.rFric * (2 * (this.rSpeed > 0) - 1);
					} else {
						this.rSpeed = 0;
					}

					//Update position
					this.absAngle += this.rSpeed;
					this.absAngle -=
						2 * Math.PI * Math.floor(this.absAngle / (2 * Math.PI) + 1 / 2);
					this.x += this.speed * Math.cos(this.absAngle);
					this.y += this.speed * Math.sin(this.absAngle);
					this.absAngle += Math.PI;
					for (var i = 0; i < this.children.length; i++) {
						this.children[i].follow(true, true);
					}
					for (var i = 0; i < this.systems.length; i++) {
						this.systems[i].update(x, y);
					}
					this.absAngle -= Math.PI;
					this.draw(true);
				}
				draw(iter) {
					var r = 4;
					ctx.beginPath();
					ctx.arc(
						this.x,
						this.y,
						r,
						Math.PI / 4 + this.absAngle,
						7 * Math.PI / 4 + this.absAngle
					);
					ctx.moveTo(
						this.x + r * Math.cos(7 * Math.PI / 4 + this.absAngle),
						this.y + r * Math.sin(7 * Math.PI / 4 + this.absAngle)
					);
					ctx.lineTo(
						this.x + r * Math.cos(this.absAngle) * 2 ** 0.5,
						this.y + r * Math.sin(this.absAngle) * 2 ** 0.5
					);
					ctx.lineTo(
						this.x + r * Math.cos(Math.PI / 4 + this.absAngle),
						this.y + r * Math.sin(Math.PI / 4 + this.absAngle)
					);
					ctx.stroke();
					if (iter) {
						for (var i = 0; i < this.children.length; i++) {
							this.children[i].draw(true);
						}
					}
				}
			}
			//Initializes and animates
			var critter;

			function setupSimple() {
				//(x,y,angle,fAccel,fFric,fRes,fThresh,rAccel,rFric,rRes,rThresh)
				var critter = new Creature(
					window.innerWidth / 2,
					window.innerHeight / 2,
					0,
					12,
					1,
					0.5,
					16,
					0.5,
					0.085,
					0.5,
					0.3
				);
				var node = critter;
				//(parent,size,angle,range,stiffness)
				for (var i = 0; i < 128; i++) {
					var node = new Segment(node, 8, 0, 3.14159 / 2, 1);
				}
				setInterval(function() {
					ctx.clearRect(0, 0, canvas.width, canvas.height);
					critter.follow(Input.mouse.x, Input.mouse.y);
				}, 33);
			}

			function setupTentacle() {
				//(x,y,angle,fAccel,fFric,fRes,fThresh,rAccel,rFric,rRes,rThresh)
				critter = new Creature(
					window.innerWidth / 2,
					window.innerHeight / 2,
					0,
					12,
					1,
					0.5,
					16,
					0.5,
					0.085,
					0.5,
					0.3
				);
				var node = critter;
				//(parent,size,angle,range,stiffness)
				for (var i = 0; i < 32; i++) {
					var node = new Segment(node, 8, 0, 2, 1);
				}
				//(end,length,speed,creature)
				var tentacle = new LimbSystem(node, 32, 8, critter);
				setInterval(function() {
					ctx.clearRect(0, 0, canvas.width, canvas.height);
					critter.follow(canvas.width / 2, canvas.height / 2);
					ctx.beginPath();
					ctx.arc(Input.mouse.x, Input.mouse.y, 2, 0, 6.283);
					ctx.fill();
				}, 33);
			}

			function setupArm() {
				//(x,y,angle,fAccel,fFric,fRes,fThresh,rAccel,rFric,rRes,rThresh)
				var critter = new Creature(
					window.innerWidth / 2,
					window.innerHeight / 2,
					0,
					12,
					1,
					0.5,
					16,
					0.5,
					0.085,
					0.5,
					0.3
				);
				var node = critter;
				//(parent,size,angle,range,stiffness)
				for (var i = 0; i < 3; i++) {
					var node = new Segment(node, 80, 0, 3.1416, 1);
				}
				var tentacle = new LimbSystem(node, 3, critter);
				setInterval(function() {
					ctx.clearRect(0, 0, canvas.width, canvas.height);
					critter.follow(canvas.width / 2, canvas.height / 2);
				}, 33);
				ctx.beginPath();
				ctx.arc(Input.mouse.x, Input.mouse.y, 2, 0, 6.283);
				ctx.fill();
			}

			function setupTestSquid(size, legs) {
				//(x,y,angle,fAccel,fFric,fRes,fThresh,rAccel,rFric,rRes,rThresh)
				critter = new Creature(
					window.innerWidth / 2,
					window.innerHeight / 2,
					0,
					size * 10,
					size * 3,
					0.5,
					16,
					0.5,
					0.085,
					0.5,
					0.3
				);
				var legNum = legs;
				var jointNum = 32;
				for (var i = 0; i < legNum; i++) {
					var node = critter;
					var ang = Math.PI / 2 * (i / (legNum - 1) - 0.5);
					for (var ii = 0; ii < jointNum; ii++) {
						var node = new Segment(
							node,
							size * 64 / jointNum,
							ang * (ii == 0),
							3.1416,
							1.2
						);
					}
					//(end,length,speed,creature,dist)
					var leg = new LegSystem(node, jointNum, size * 30, critter);
				}
				setInterval(function() {
					ctx.clearRect(0, 0, canvas.width, canvas.height);
					critter.follow(Input.mouse.x, Input.mouse.y);
				}, 33);
			}

			function setupLizard(size, legs, tail) {
				var s = size;
				//(x,y,angle,fAccel,fFric,fRes,fThresh,rAccel,rFric,rRes,rThresh)
				critter = new Creature(
					window.innerWidth / 2,
					window.innerHeight / 2,
					0,
					s * 10,
					s * 2,
					0.5,
					16,
					0.5,
					0.085,
					0.5,
					0.3
				);
				var spinal = critter;
				//(parent,size,angle,range,stiffness)
				//Neck
				for (var i = 0; i < 6; i++) {
					spinal = new Segment(spinal, s * 4, 0, 3.1415 * 2 / 3, 1.1);
					for (var ii = -1; ii <= 1; ii += 2) {
						var node = new Segment(spinal, s * 3, ii, 0.1, 2);
						for (var iii = 0; iii < 3; iii++) {
							node = new Segment(node, s * 0.1, -ii * 0.1, 0.1, 2);
						}
					}
				}
				//Torso and legs
				for (var i = 0; i < legs; i++) {
					if (i > 0) {
						//Vertebrae and ribs
						for (var ii = 0; ii < 6; ii++) {
							spinal = new Segment(spinal, s * 4, 0, 1.571, 1.5);
							for (var iii = -1; iii <= 1; iii += 2) {
								var node = new Segment(spinal, s * 3, iii * 1.571, 0.1, 1.5);
								for (var iv = 0; iv < 3; iv++) {
									node = new Segment(node, s * 3, -iii * 0.3, 0.1, 2);
								}
							}
						}
					}
					//Legs and shoulders
					for (var ii = -1; ii <= 1; ii += 2) {
						var node = new Segment(spinal, s * 12, ii * 0.785, 0, 8); //Hip
						node = new Segment(node, s * 16, -ii * 0.785, 6.28, 1); //Humerus
						node = new Segment(node, s * 16, ii * 1.571, 3.1415, 2); //Forearm
						for (
							var iii = 0; iii < 4; iii++ //fingers
						) {
							new Segment(node, s * 4, (iii / 3 - 0.5) * 1.571, 0.1, 4);
						}
						new LegSystem(node, 3, s * 12, critter, 4);
					}
				}
				//Tail
				for (var i = 0; i < tail; i++) {
					spinal = new Segment(spinal, s * 4, 0, 3.1415 * 2 / 3, 1.1);
					for (var ii = -1; ii <= 1; ii += 2) {
						var node = new Segment(spinal, s * 3, ii, 0.1, 2);
						for (var iii = 0; iii < 3; iii++) {
							node = new Segment(node, s * 3 * (tail - i) / tail, -ii * 0.1, 0.1, 2);
						}
					}
				}
				setInterval(function() {
					ctx.clearRect(0, 0, canvas.width, canvas.height);
					critter.follow(Input.mouse.x, Input.mouse.y);
				}, 33);
			}
			canvas.style.backgroundColor = "black";
			ctx.strokeStyle = "white";
			//setupSimple();//Just the very basic string
			//setupTentacle();//Tentacle that reaches for mouse
			//setupLizard(.5,100,128);//Literal centipede
			//setupSquid(2,8);//Spidery thing
			var legNum = Math.floor(1 + Math.random() * 12);
			setupLizard(
				8 / Math.sqrt(legNum),
				legNum,
				Math.floor(4 + Math.random() * legNum * 8)
			);
		</script>

	</body>
</html>
